package com.myssm.ioc;

import com.myssm.util.StringUtil;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

/**
 * @author Jack Tang
 * @description
 * @create 2023-05-12 21:12
 */
public class ClassPathXmlApplicationContext implements BeanFactory{

    private Map<String,Object> beanMap = new HashMap<String, Object>();

    private String path = "applicationContext.xml";
    public ClassPathXmlApplicationContext(){
        this("applicationContext.xml");
    }
    public ClassPathXmlApplicationContext(String path){
        if (StringUtil.isEmpty(path)){
            throw new RuntimeException("IOC容器的配置文件没有指定......");
        }
        try {
            //这段代码的作用是读取项目中的 applicationContext.xml 文件，并将其解析为一个 Document 对象。
            // 利用反射获取文件中的InputStream流
            InputStream inputStream = getClass().getClassLoader().getResourceAsStream(path);
            // 然后使用 DocumentBuilderFactory 和 DocumentBuilder 对象将该流解析为一个 Document 对象
            // 1.创建 DocumentBuilderFactory 对象
            DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
            // 2.创建 DocumentBuilder 对象
            DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
            // 使用 DocumentBuilder 对象的 parse() 方法将 InputStream 流中的 XML 数据解析为一个 Document 对象
            // 3.创建 Document 对象
            Document document = documentBuilder.parse(inputStream);



            // 4.获取所有的bean节点
            NodeList beaNodeList = document.getElementsByTagName("bean");
            // 遍历所有的 bean 标签
            for (int i = 0; i < beaNodeList.getLength(); i++) {
                Node beaNode = beaNodeList.item(i);
                if (beaNode.getNodeType() == Node.ELEMENT_NODE) {
                    Element beaElement = (Element) beaNode;
                    String beanId = beaElement.getAttribute("id");
                    String className = beaElement.getAttribute("class");
                    // Class.forName(className) 方法最终返回的是一个 Class 对象
                    Class beanClass = Class.forName(className);
                    // beanClass.newInstance() 方法会创建一个 beanClass 类的实例，并返回该实例的引用。
                    Object beanObj = beanClass.newInstance();
                    beanMap.put(beanId, beanObj);
                }
            }
            // 5.组装bean之间的依赖关系
            for (int i = 0; i < beaNodeList.getLength(); i++) {
                Node beaNode = beaNodeList.item(i);
                if (beaNode.getNodeType() == Node.ELEMENT_NODE) {
                    Element beanElement = (Element) beaNode;
                    // 获取当前 bean 标签下的所有子节点
                    NodeList beanChildNodeList = beanElement.getChildNodes();
                    String beanId = beanElement.getAttribute("id");
                    for (int j = 0; j < beanChildNodeList.getLength(); j++) {
                        Node beanChildNode = beanChildNodeList.item(j);
                        // 遍历其所有子标签，查找 property 标签
                        // 如果存在 property 标签，就将该 property 标签中 ref 属性所指向的实例对象设置到当前 bean 对应的实例的 property 属性上去。
                        if (beanChildNode.getNodeType() == Node.ELEMENT_NODE && "property".equals(beanChildNode.getNodeName())) {
                            Element propertyElement = (Element) beanChildNode;
                            String propertyName = propertyElement.getAttribute("name");
                            String propertyRef = propertyElement.getAttribute("ref");
                            // 2) 找到 propertyRef 对应的实例
                            Object refObj = beanMap.get(propertyRef);
                            // 3) 将 refObj 设置到当前bean对应的实例的property属性上去
                            Object beanObj = beanMap.get(beanId);
                            Class beanClazz = beanObj.getClass();
                            // 当前 bean 类型的指定属性名为 propertyName 的 Field 对象
                            Field propertyField = beanClazz.getDeclaredField(propertyName);
                            propertyField.setAccessible(true);
                            propertyField.set(beanObj,refObj);
                        }
                    }


                }
            }
        } catch (ParserConfigurationException e) {
            e.printStackTrace();
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (SAXException e) {
            throw new RuntimeException(e);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (NoSuchFieldException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public Object getBean(String id) {
        return beanMap.get(id);
    }
}